/*
 * Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * This file is part of EvoSuite.
 *
 * EvoSuite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 *
 * EvoSuite is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
 */

package org.evosuite.testcase.statements;

import org.evosuite.Properties;
import org.evosuite.seeding.ConstantPool;
import org.evosuite.seeding.ConstantPoolManager;
import org.evosuite.testcase.TestCase;
import org.evosuite.testcase.execution.CodeUnderTestException;
import org.evosuite.testcase.execution.Scope;
import org.evosuite.utils.Randomness;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;

/**
 * <p>
 * StringPrimitiveStatement class.
 * </p>
 *
 * @author fraser
 */
public class StringPrimitiveStatement extends PrimitiveStatement<String> {

    private static final long serialVersionUID = 274445526699835887L;

    /**
     * <p>
     * Constructor for StringPrimitiveStatement.
     * </p>
     *
     * @param tc    a {@link org.evosuite.testcase.TestCase} object.
     * @param value a {@link java.lang.String} object.
     */
    public StringPrimitiveStatement(TestCase tc, String value) {
        super(tc, String.class, value);
    }

    /**
     * <p>
     * Constructor for StringPrimitiveStatement.
     * </p>
     *
     * @param tc a {@link org.evosuite.testcase.TestCase} object.
     */
    public StringPrimitiveStatement(TestCase tc) {
        super(tc, String.class, "");
    }

    /* (non-Javadoc)
     * @see org.evosuite.testcase.PrimitiveStatement#zero()
     */

    /**
     * {@inheritDoc}
     */
    @Override
    public void zero() {
        value = "";
    }

    private static String removeCharAt(String s, int pos) {
        return s.substring(0, pos) + s.substring(pos + 1);
    }

    private static String replaceCharAt(String s, int pos, char c) {
        return s.substring(0, pos) + c + s.substring(pos + 1);
    }

    private static String insertCharAt(String s, int pos, char c) {
        return s.substring(0, pos) + c + s.substring(pos);
    }

    private String StringInsert(String s, int pos) {
        final double ALPHA = 0.5;
        int count = 1;

        while (Randomness.nextDouble() <= Math.pow(ALPHA, count)
                && s.length() < Properties.STRING_LENGTH) {
            count++;
            // logger.info("Before insert: '"+s+"'");
            s = insertCharAt(s, pos, Randomness.nextChar());
            // logger.info("After insert: '"+s+"'");
        }
        return s;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delta() {

        String s = value;
        if (s == null) {
            randomize();
            return;
        }

        final double P2 = 1d / 3d;
        double P = 1d / s.length();
        // Delete
        if (Randomness.nextDouble() < P2) {
            for (int i = s.length(); i > 0; i--) {
                if (Randomness.nextDouble() < P) {
                    // logger.info("Before remove at "+i+": '"+s+"'");
                    s = removeCharAt(s, i - 1);
                    // logger.info("After remove: '"+s+"'");
                }
            }
        }
        P = 1d / s.length();
        // Change
        if (Randomness.nextDouble() < P2) {
            for (int i = 0; i < s.length(); i++) {
                if (Randomness.nextDouble() < P) {
                    // logger.info("Before change: '"+s+"'");
                    s = replaceCharAt(s, i, Randomness.nextChar());
                    // logger.info("After change: '"+s+"'");
                }
            }
        }

        // Insert
        if (Randomness.nextDouble() < P2) {
            // for(int i = 0; i < s.length(); i++) {
            // if(Randomness.nextDouble() < P) {
            int pos = 0;
            if (s.length() > 0)
                pos = Randomness.nextInt(s.length());
            s = StringInsert(s, pos);
            // }
            // }
        }
        value = s;
        // logger.info("Mutated string now is: "+value);
    }

    /* (non-Javadoc)
     * @see org.evosuite.testcase.PrimitiveStatement#increment(java.lang.Object)
     */

    /**
     * <p>
     * increment
     * </p>
     */
    public void increment() {
        String s = value;
        if (s == null) {
            randomize();
            return;
        } else if (s.isEmpty()) {
            s += Randomness.nextChar();
        } else {
            s = replaceCharAt(s, Randomness.nextInt(s.length()), Randomness.nextChar());
        }

        value = s;
    }

    /* (non-Javadoc)
     * @see org.evosuite.testcase.PrimitiveStatement#randomize()
     */

    /**
     * {@inheritDoc}
     */
    @Override
    public void randomize() {
        if (Randomness.nextDouble() >= Properties.PRIMITIVE_POOL)
            value = Randomness.nextString(Randomness.nextInt(Properties.STRING_LENGTH));
        else {
            ConstantPool constantPool = ConstantPoolManager.getInstance().getConstantPool();
            String candidateString = constantPool.getRandomString();
            if (Properties.MAX_STRING > 0 && candidateString.length() < Properties.MAX_STRING)
                value = candidateString;
            else
                value = Randomness.nextString(Randomness.nextInt(Properties.STRING_LENGTH));
        }
    }

    @Override
    public Throwable execute(Scope scope, PrintStream out)
            throws InvocationTargetException, IllegalArgumentException,
            IllegalAccessException, InstantiationException {
        Throwable exceptionThrown = null;

        try {
            if (value == null)
                retval.setObject(scope, null);
            else {
                // String literals may not be longer than 32767
                if (value.length() >= 32767)
                    throw new CodeUnderTestException(new IllegalArgumentException("Maximum string length exceeded"));

                // In the JUnit code we produce, strings are generated as
                // String foo = "bar";
                // That means any reference comparison will behave different
                // as internally value is created as String foo = new String("bar").
                // Therefore we have to use the string object in the constant pool
                retval.setObject(scope, value.intern());
            }
        } catch (CodeUnderTestException e) {
            exceptionThrown = e;
        }
        return exceptionThrown;
    }

    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(value);
    }

    private void readObject(ObjectInputStream ois) throws ClassNotFoundException,
            IOException {
        ois.defaultReadObject();
        value = (String) ois.readObject();
    }
}
